#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "label.h"
#include "register.h"
#include "MachineDependent.h"
#include "AAT.h"

FILE *outfile;
char *heapSufixName = "_HEAP";
char *heapSufixSize = "_HEAPSIZE";
int MAX_IDENT_LENGTH = 100;

void generateStatement(AATstatement tree); 
void generateExpression(AATexpression tree);
void generateMove(AATstatement tree);
void generateMemoryExpression(AATexpression tree);


void doBinaryOperation(AATexpression tree);
void recalculateMemoryAddress();

// for testing purpose
void printAtomVariable();
void printAtomDirectVariable();


void emit(char *assem,...);
void emitLibrary(void);
void emitSetupCode(void);
int emitDataCode(AATstatement tree);



void generateCode(AATstatement tree, FILE *output) {
  outfile = output; 
  if(emitDataCode(tree)==0){
	  emitSetupCode();
	  emit("main1:");
	  generateStatement(tree->u.sequential.right);
	  emit("jr %s", ReturnAddr());
	  fprintf(output,"\n");
	  emitLibrary();
   } else
	printf("ERROR!!! ASSEMBLY FILE GENERATION FAILED\n");
}



void emit(char *assem,...) {
  va_list ap;
  if (assem[strlen(assem)-1] != ':') 
    fprintf(outfile,"\t");
 va_start(ap,assem);
  vfprintf(outfile, assem, ap);
  va_end(ap);
  fprintf(outfile,"\n");
}

int emitDataCode(AATstatement tree){
  emit(".data");
  emit("cr:");
  emit(".asciiz \"\\n\"");
  emit("sp:");
  emit(".asciiz \" \"");
  emit("CURRENT_HEAPPTR:");
  emit(".word 0");
  emit("CURRENT_HEAPSIZE:");
  emit(".word 0");


	if(tree->kind != AAT_SEQ ){
		printf("USE SEQUENTIAL STATEMENT AS FIRST!\n");
		return -1;
	} else{
		AATstatement leftStatement = tree->u.sequential.left;
		if( 
			(leftStatement->kind != AAT_PROCEDURECALL) || 
			( leftStatement->kind == AAT_PROCEDURECALL && strcmp(GetLabelName(leftStatement->u.procedureCall.jump), "allocateMemory")!=0)
		){
			printf("FIRSTLY ALLOCATE MEMORY FOR EACH BLOCK IN SEQUENTIAL STATEMENT, THAN THE REST!\n");
			return -1;
		} else
			// create SPIM data code
			{
				AATexpressionList blocksList= leftStatement->u.procedureCall.actuals;
				AATexpression block;
				char name[MAX_IDENT_LENGTH];
				int mainBlockDeclaration = 1;
				while(blocksList!=NULL){
					block=blocksList->first;
					blocksList = blocksList->rest;
					
					if(block->kind!=AAT_MEMALLOC){
						printf("MUST BE AAT_MEMALLOC EXPRESSION!\n");
						return -1;
					}
					
					if(	strcmp(GetLabelName(block->u.memoryCell.blockName), "main")==0 )
						mainBlockDeclaration = 0;

					memset((void*)&name, 0, sizeof(char)*MAX_IDENT_LENGTH);
					strcpy(name, GetLabelName(block->u.memoryCell.blockName));
					strcat(name, heapSufixName);
					emit("%s:", name);
					emit(".word 0");
					
					memset((void*)&name, 0, sizeof(char)*100);
					strcpy(name, GetLabelName(block->u.memoryCell.blockName));
					strcat(name, heapSufixSize);
					emit("%s:", name);
					AATexpression constantMemExp = block->u.memoryCell.memoryOffset;
					emit(".word %d",constantMemExp->u.constant);					

				}
	
				if(mainBlockDeclaration==1){
					printf("'main' BLOCK BODY MUST BE DECLARE !\n");
					return -1;
				}
				

			}

	}


  return 0;
}

void emitSetupCode(void) {
  emit(".text");
  emit(".globl main");
  emit("main:");

/*
				emit("la %s, main_HEAP", Tmp1());
				emit("lw %s, 0(%s)", Tmp3(), Tmp1());
				emit("move %s, %s", Acc(), Tmp3());
				printAtomDirectVariable();
*/

		//save current heap size ( main )
		emit("la %s, main_HEAPSIZE", Tmp1());
		emit("la %s, CURRENT_HEAPSIZE", Tmp2());
		emit("lw %s, 0(%s)", Tmp3(), Tmp1());
		emit("sw %s, 0(%s)", Tmp3(), Tmp2());


  emit("addi %s,%s,0", AccSP(), SP());
  emit("addi %s, %s, %d", SP(), SP(), - WORDSIZE * STACKSIZE);
  emit("addi %s, %s, 0", Tmp1(), SP());
  emit("addi %s, %s, %d", Tmp1(), Tmp1(), - WORDSIZE * STACKSIZE);


  emit("la %s, main_HEAP", Tmp2());  
  emit("sw %s, 0(%s)", Tmp1(), Tmp2());

  emit("la %s, CURRENT_HEAPPTR", Tmp2());
  emit("sw %s, 0(%s)", Tmp1(), Tmp2());

  emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());
  emit("jal main1");
  emit("lw %s,%d(%s)", ReturnAddr(), WORDSIZE, SP());
  emit("jr %s", ReturnAddr());
}

void generateStatement(AATstatement tree) {

  switch (tree->kind) {
	case AAT_MOVE:
		generateMove(tree);
    	break;
  
	case AAT_SEQ:
		generateStatement(tree->u.sequential.left);
		generateStatement(tree->u.sequential.right);
	break;    

	case AAT_EMPTY:
	break;

	case AAT_LABEL:
		emit("%s:", GetLabelName(tree->u.label));
	break;

	case AAT_CONDITIONALJUMP:
		generateExpression(tree->u.conditionalJump.test);
		emit("bnez %s, %s", Acc(), GetLabelName(tree->u.conditionalJump.jump));
	break;

	case AAT_JUMP:
		emit("j %s",GetLabelName(tree->u.jump) );
	break;

	case AAT_PROCEDUREDEF:
		{

			// it's definition - don't execute this code in the definition state (just jump over it)
			emit("j %s", GetLabelName(tree->u.procedureDef.end));

			// start of the procedure definition 
			emit("%s:", GetLabelName(tree->u.procedureDef.start));


			//tmp variables for loop start and loop end names
		
			char popProcArgsLoopStart[MAX_IDENT_LENGTH];
			memset((void*)&popProcArgsLoopStart, 0, sizeof(char)*MAX_IDENT_LENGTH);
			strcpy(popProcArgsLoopStart, "PopProcArgsLoop");
			strcat(popProcArgsLoopStart, GetLabelName(tree->u.procedureDef.start));

			char popProcArgsLoopEnd[MAX_IDENT_LENGTH];
			memset((void*)&popProcArgsLoopEnd, 0, sizeof(char)*MAX_IDENT_LENGTH);
			strcpy(popProcArgsLoopEnd, "PopProcArgsLoopEnd");
			strcat(popProcArgsLoopEnd, GetLabelName(tree->u.procedureDef.start));

			// pop all arguments and copy them to the heap

			//if there is no arguments jump over copy procedure
			emit("move %s, %s", Tmp1(), Zero());
			emit("addi %s, %s, %d", Tmp1(), Tmp1(), tree->u.procedureDef.argsSize);
			emit("blez %s, %s", Tmp1(), popProcArgsLoopEnd );

			emit("move %s, %s", Tmp1(), Zero()); //initialize counter to 0
			emit("%s:",popProcArgsLoopStart);
		
			// copy commands
			emit("lw %s,%d(%s)", Tmp2(), WORDSIZE, SP());
			emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);
			emit("la %s, CURRENT_HEAPPTR", Tmp3());
			emit("lw %s, 0(%s)", Tmp3(), Tmp3());
			emit("add %s, %s, %s", Tmp3(), Tmp3(), Tmp1());
			emit("sw %s, 0(%s)", Tmp2() , Tmp3());				

			emit("addi %s, %s, %d", Tmp1(), Tmp1(), 4); //increment counter by 4
			emit("blt %s, %d, %s", Tmp1(), tree->u.procedureDef.argsSize, popProcArgsLoopStart); // if counter < size of arguments then continue copy loop

			emit("%s:",popProcArgsLoopEnd);
			

			// do the procedure body
			generateStatement(tree->u.procedureDef.body);
		
			//return
			emit("jr %s", ReturnAddr());
			// end of the procedure definition
			emit("%s:", GetLabelName(tree->u.procedureDef.end));
		}

	break;

	// temporary only for printAtomVariable Purpose
	case AAT_PROCEDURECALL:
		if(strcmp(GetLabelName(tree->u.procedureCall.jump), "printAtomVariable")==0)
		{
			generateExpression(tree->u.procedureCall.actuals->first);
			emit("move %s, %s", Tmp2(), Acc());
			//push the return address
			emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
			emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());
			//push print argument
			emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
			emit("sw %s, %d(%s)", Tmp2() , WORDSIZE, SP());
			//go to print function
			emit("jal Print");
			//restore ReturnAddr()
			emit("lw %s,%d(%s)", ReturnAddr(), WORDSIZE, SP());
			emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);			

		} else
		if(strcmp(GetLabelName(tree->u.procedureCall.jump), "printLine")==0){
				//push the return address
				emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
				emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());
				emit("jal PrintLn");
				//restore ReturnAddr()
				emit("lw %s,%d(%s)", ReturnAddr(), WORDSIZE, SP());
				emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);	
		} else
		{

			// 1. push the return address
			emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
			emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());

			// 2. push CURRENT_HEAPPTR
			emit("la %s, CURRENT_HEAPPTR", Tmp1());
			emit("lw %s,0(%s)" ,Tmp2(),Tmp1());
			emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
			emit("sw %s, %d(%s)", Tmp2() , WORDSIZE, SP());

			// xxx. push CURRENT_HEAPSIZE
			emit("la %s, CURRENT_HEAPSIZE", Tmp1());
			emit("lw %s, 0(%s)", Tmp2(), Tmp1());		//Tmp2 stores current heapsize			
			emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
			emit("sw %s, %d(%s)", Tmp2() , WORDSIZE, SP());			

			// 3. push all arguments
			AATexpressionList actuals = tree->u.procedureCall.actuals;
			while(actuals!=NULL){
				AATexpression ex = actuals->first;
				generateExpression(ex);
				emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
				emit("sw %s, %d(%s)", Acc() , WORDSIZE, SP());
				actuals = actuals->rest;
			}

			// create stack size (block name + heapsize sufix) spim variable reference
			char name[MAX_IDENT_LENGTH];
			memset((void*)&name, 0, sizeof(char)*MAX_IDENT_LENGTH);
			strcpy(name, GetLabelName(tree->u.procedureCall.jump));
			strcat(name, heapSufixSize);


			// 4. add to CURRENT_HEAPPTR previous block data size
			emit("la %s, CURRENT_HEAPPTR", Tmp1());
			emit("lw %s,0(%s)" ,Tmp2(),Tmp1());
//			emit("la %s, %s", Tmp1(), name);
//			emit("lw %s,0(%s)" ,Tmp3(),Tmp1());			
				//!!!!!!!!!!!!!!!!!!!!!!!!!!!!
				emit("la %s, CURRENT_HEAPSIZE", Tmp1());
				emit("lw %s, 0(%s)", Tmp3(), Tmp1());		//Tmp2 stores current heapsize
				//!!!!!!!!!!!!!!!!!!!!!!!!!!!!		

			emit("add %s, %s, %s", Tmp2(), Tmp2(), Tmp3());
			emit("la %s, CURRENT_HEAPPTR", Tmp1());
			emit("sw %s, 0(%s)", Tmp2(), Tmp1());

				//change CURRENT_HEAPSIZE to called procedure heapsize
				emit("la %s, %s", Tmp1(), name);
				emit("lw %s,0(%s)" ,Tmp3(),Tmp1());						
				emit("la %s, CURRENT_HEAPSIZE", Tmp1());				
				emit("sw %s,0(%s)", Tmp3(), Tmp1());

			// create stack heap ptr (block name + heapname sufix) spim variable reference
			memset((void*)&name, 0, sizeof(char)*MAX_IDENT_LENGTH);
			strcpy(name, GetLabelName(tree->u.procedureCall.jump));
			strcat(name, heapSufixName);

			//5. set procedure name _HEAP to CURRENT_HEAPPTR
			emit("la %s, %s", Tmp1(),name);
			emit("sw %s, 0(%s)", Tmp2(), Tmp1());



			// 6. jump to procedure body
			emit("jal %s",tree->u.procedureCall.jump);

			// xxx. pop CURRENT_HEAPSIZE
			emit("lw %s,%d(%s)", Tmp2(), WORDSIZE, SP());
			emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);	
			emit("la %s, CURRENT_HEAPSIZE", Tmp1());
			emit("sw %s, 0(%s)", Tmp2(), Tmp1());

			// 7. pop and restore CURRENT_HEAPPTR
			emit("lw %s,%d(%s)", Tmp2(), WORDSIZE, SP());
			emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);	
			emit("la %s, CURRENT_HEAPPTR", Tmp1());
			emit("sw %s,0(%s)" ,Tmp2(),Tmp1());


			//8. set procedure name _HEAP to CURRENT_HEAPPTR
			emit("la %s, %s", Tmp1(),name);
			emit("sw %s, 0(%s)", Tmp2(), Tmp1());
			

			//9. pop and restore return address
			emit("lw %s,%d(%s)", ReturnAddr(), WORDSIZE, SP());
			emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);	
			
		}

	break;
		
    /* more cases */
  }
}

void generateMove(AATstatement tree) {

  switch(tree->u.move.lhs->kind){

	  case AAT_REGISTER:
	    generateExpression(tree->u.move.rhs);
	    emit("addi %s,%s,0 ",tree->u.move.lhs->u.reg,Acc());
	  break;


	  case AAT_MEMORY:
	   // fprintf(outfile,"############################\n");
	   // fprintf(outfile,"# genetare move to memory\n");
	   // fprintf(outfile,"############################\n");
	    generateExpression(tree->u.move.rhs);
	    emit("sw %s, 0(%s)",Acc(),AccSP());
	    emit("addi %s,%s,%d",AccSP(),AccSP(),0-WORDSIZE);
	     generateExpression(tree->u.move.lhs->u.memoryCell.memoryOffset);
	    emit("addi %s,%s,%d",AccSP(),AccSP(),WORDSIZE);    
	    emit("lw %s,0(%s)" ,Tmp1(),AccSP());	// result of the right operand
	    recalculateMemoryAddress(tree->u.move.lhs->u.memoryCell.blockName);	// in Acc exists offset of used memory cell It must be recalculated (having in account HEAP)
	    emit("sw %s,0(%s) ",Tmp1(),Acc());

	    //printHeap2();
	  break;


	  default:
	    fprintf(stderr,"Bad MOVE node -- LHS should be T_mem or T_register\n");
	  break;

  }

}

void recalculateMemoryAddress(Label blockName){
//fprintf(outfile,"############################\n");
//fprintf(outfile,"# recalculate memory address\n");
//fprintf(outfile,"############################\n");
  char name[MAX_IDENT_LENGTH];
  memset((void*)&name, 0, sizeof(char)*MAX_IDENT_LENGTH);
  strcpy(name, blockName);
  strcat(name, heapSufixName);

/*
				emit("la $s1, %s",name);
				emit("lw $s2, 0($s1)");
				emit("move $s3, %s", Acc());
				emit("move %s, $s2", Acc());
				printAtomDirectVariable();
				emit("move %s, $s3",Acc());*/

//  emit("la %s, CURRENT_HEAPPTR", Tmp2());
  emit("la %s, %s", Tmp2(), name);
  emit("lw %s,0(%s)" ,Tmp3(),Tmp2());;
  emit("add %s, %s, %s", Tmp3(),Tmp3(), Acc());
  emit("move %s, %s", Acc(), Tmp3());



}


void generateExpression(AATexpression tree){

  switch(tree->kind){
	case  AAT_CONSTANT:
		    //fprintf(outfile,"############################\n");
		    //fprintf(outfile,"# generate constant expression\n");
		   // fprintf(outfile,"############################\n");
		emit("li %s, %d", Acc(), tree->u.constant);
	break;



	case AAT_MEMORY:
		generateExpression(tree->u.memoryCell.memoryOffset);
		//emit("li %s, %d", Acc(),  tree->u.memory->u.constant); !!!!!!!!!!!!
		recalculateMemoryAddress(tree->u.memoryCell.blockName);
		emit("lw %s, 0(%s)", Acc(), Acc());
	break;

	case  AAT_MEMALLOC:
		
		//printHeap();	
		//push the return address
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());

		//push size to allocation
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("move %s, %s",Tmp1(), Zero());
		emit("addi %s, %s, %d", Tmp1(), Tmp1(), tree->u.constant);
		emit("sw %s, %d(%s)", Tmp1() , WORDSIZE, SP());
		emit("jal allocate");

		//pop the return address
		emit("lw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());
		emit("addi %s,%s,%d",SP(),SP(),0-WORDSIZE);
		//printHeap();

	break;

		
/*
	case AAT_FUNCTIONCALL:
		//push the return address
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());
		//push procedure invocation parameters on the stack
		AATexpressionList list = tree->u.functionCall.actuals;
		while(list!=NULL){
			AATexpression act = list->first;
			if(strcmp(act->u.

		}
*/


	case AAT_OPERATOR:
		if(tree->u.operator.op == AAT_PLUS){
			doBinaryOperation(tree);
			emit("add %s, %s, %s", Acc(), Acc(), Tmp1()); 
		} else
		if(tree->u.operator.op == AAT_MINUS){
			doBinaryOperation(tree);
			emit("sub %s, %s, %s", Acc(), Acc(), Tmp1()); 
		} else
		if(tree->u.operator.op == AAT_MULTIPLY){
			doBinaryOperation(tree);
			emit("mul %s, %s, %s", Acc(), Acc(), Tmp1()); 
		} else
		if(tree->u.operator.op == AAT_DIVIDE){
			doBinaryOperation(tree);
			emit("div %s, %s", Acc(), Tmp1()); 
			emit("mflo %s", Acc());
		} else
		if(tree->u.operator.op == AAT_MODULO){
			doBinaryOperation(tree);
			emit("div %s, %s", Acc(), Tmp1()); 
			emit("mfhi %s", Acc());
		} else
		if(tree->u.operator.op == AAT_NEQ){
			doBinaryOperation(tree);
			emit("sne %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc!=Tmp1) then Acc:= 1, else Acc:=0
		} else
		if(tree->u.operator.op == AAT_EQ){
			doBinaryOperation(tree);
			emit("seq %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc==Tmp1) then Acc:= 1, else Acc:=0
		} else
		if(tree->u.operator.op == AAT_LT){
			doBinaryOperation(tree);
			emit("slt %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc<Tmp1) then Acc:= 1, else Acc:=0
		} else
		if(tree->u.operator.op == AAT_GT){
			doBinaryOperation(tree);
			emit("sgt %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc>Tmp1) then Acc:= 1, else Acc:=0
		} else
		if(tree->u.operator.op == AAT_NEQ){
			doBinaryOperation(tree);
			emit("sne %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc<>Tmp1) then Acc:= 1, else Acc:=0
		} else
		if(tree->u.operator.op == AAT_LEQ){
			doBinaryOperation(tree);
			emit("sle %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc<=Tmp1) then Acc:= 1, else Acc:=0
		} else
		if(tree->u.operator.op == AAT_GEQ){
			doBinaryOperation(tree);
			emit("sge %s, %s, %s", Acc(), Acc(), Tmp1()); // if(Acc<=Tmp1) then Acc:= 1, else Acc:=0
		}
	break;


  }

}

//out: Tmp1() --> the resul of right operand
//out: Acc() --> the result of the left operand
void doBinaryOperation(AATexpression tree){
		generateExpression(tree->u.operator.right); // right site of operator expression
		//push Acc() - push right operand on the ACC stack
		emit("sw %s, 0(%s)",Acc(),AccSP());
		emit("addi %s,%s,%d",AccSP(),AccSP(),0-WORDSIZE);	
		generateExpression(tree->u.operator.left); // left site of operator expression		
		//pop Tmp1() - pop result of the right operator
    		emit("addi %s,%s,%d",AccSP(),AccSP(),WORDSIZE);    
   		emit("lw %s,0(%s)" ,Tmp1(),AccSP());
}




void printAtomDirectVariable(){
		emit("move %s, %s", Tmp2(), Acc());

		//push the return address
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());

		//push print argument
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("sw %s, %d(%s)", Tmp2() , WORDSIZE, SP());

		//go to print function
		emit("jal Print");

		//restore ReturnAddr()
		  emit("lw %s,%d(%s)", ReturnAddr(), WORDSIZE, SP());
		  emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);
}

// Acc has address to the variable
void printAtomVariable(){
		emit("lw %s, 0(%s)", Tmp2(), Acc());

		//push the return address
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("sw %s, %d(%s)", ReturnAddr() , WORDSIZE, SP());

		//push print argument
		emit("addi %s,%s,%d",SP(),SP(),WORDSIZE);
		emit("sw %s, %d(%s)", Tmp2() , WORDSIZE, SP());

		//go to print function
		emit("jal Print");

		//restore ReturnAddr()
		  emit("lw %s,%d(%s)", ReturnAddr(), WORDSIZE, SP());
		  emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);
}


void emitLibrary() {

  emit("Print:");
  emit("lw $a0, %d(%s)",WORDSIZE,SP());
  emit("addi %s, %s, %d", SP(), SP(), 0-WORDSIZE);
  emit("li $v0, 1");
  emit("syscall");
  emit("li $v0,4");
  emit("la $a0, sp");
  emit("syscall");
  emit("jr $ra");

  emit("PrintLn:");
  emit("li $v0,4");
  emit("la $a0, cr");
  emit("syscall");
  emit("jr $ra");




}
